/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package clasificador.genetico.reglas;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import utils.Tuple;
import clasificador.genetico.FitnessFunction;
import datos.Atributo;
import datos.AtributoFinito;
import datos.Dato;
import datos.MuestraClasificada;

/**
 *
 * @author e237573
 */
public final class RuleSetFitness2<Clase>
        implements FitnessFunction<TGenotipo> {

    private MuestraClasificada<Clase> muestra;
    private ArrayList<Tuple<int[], Integer>> muestraNormalizada;
    private Integer entradasTest;
    private Map<Atributo, List> posiblesValores;
    private Random r;
    private double sizePenalty;

    public RuleSetFitness2(MuestraClasificada<Clase> muestra, double sizePenalty) {
        this.r = new Random();
        this.muestra = muestra;
        this.posiblesValores = new HashMap<>();
        for (Atributo a : muestra.getAtributos()) {
            if (a instanceof AtributoFinito) {
                List s = ((AtributoFinito) a).getRango();
                if (s.size() > Integer.SIZE) {
                    throw new RuntimeException("El atributo " + a + " tiene demasiados"
                            + " valores");
                } else {
                    this.posiblesValores.put(a, s);
                }
            } else {
                throw new RuntimeException("El atributo " + a + " no es finito");
            }
        }
        this.muestraNormalizada = new ArrayList<>(this.muestra.size());
        for (Dato d : muestra.getDatos()) {
            this.muestraNormalizada.add(this.normalizar(d));
        }
        this.entradasTest = 0;
        this.sizePenalty = sizePenalty;
    }

    public RuleSetFitness2(MuestraClasificada<Clase> muestra, double sizePenalty, Double proporcionTest) {
        this(muestra,sizePenalty);
        this.entradasTest = ((Double) (proporcionTest * this.muestraNormalizada.size())).intValue();
    }

    public Tuple<int[], Integer> normalizar(Dato d) {
        int[] valoresAtributos = new int[muestra.getAtributos().size() - 1];
        int atributoActual = 0;

        Integer clase = 0;
        for (Atributo a : this.muestra.getAtributos()) {
            int index = this.posiblesValores.get(a).indexOf(d.getAtributo(a));
            if (a.equals(this.muestra.getClase())) {
                clase = 1 << index;
            } else {
                valoresAtributos[atributoActual] = (1 << index);
                atributoActual++;
            }
        }
        return new Tuple<>(valoresAtributos, clase);
    }

    public Integer clasificar(TGenotipo i, int[] d) {
        /* MAJYCKS */
        clases:
        for (Tuple<int[], Integer> regla : i) {
            for (int atr = 0; atr < regla.l().length; atr++) {
                if (0 == (regla.l()[atr] & d[atr])) {
                    continue clases;
                }
            }
            return regla.r();
        }
        return 0;
    }

    @Override
    public Double f(TGenotipo reglas) {
        Double fitness = 0.0;

        if (this.entradasTest == 0) {
            Double increment = 1.0 / this.muestraNormalizada.size();
            for (Tuple<int[], Integer> e : this.muestraNormalizada) {
                int[] atributos = e.l();
                Integer clase = e.r();

                if (clase.equals(clasificar(reglas, atributos))) {
                    fitness += increment;
                }
            }
        } else {
            Double increment = 1.0 / this.entradasTest;

            for (int i = 0; i < this.entradasTest; i++) {
                int index = r.nextInt(this.muestraNormalizada.size());
                int[] atributos = this.muestraNormalizada.get(index).l();
                Integer clase = this.muestraNormalizada.get(index).r();

                if (clase.equals(clasificar(reglas, atributos))) {
                    fitness += increment;
                }
            }
        }

        return fitness/(1+sizePenalty*reglas.size());
    }
    
    @Override
    public Double precise(TGenotipo reglas) {
        Double fitness = 0.0;

        Double increment = 1.0 / this.muestraNormalizada.size();
        for (Tuple<int[], Integer> e : this.muestraNormalizada) {
            int[] atributos = e.l();
            Integer clase = e.r();

            if (clase.equals(clasificar(reglas, atributos))) {
                fitness += increment;
            }
        }
        return fitness/(1+sizePenalty*reglas.size());
    }
}
